package com.liyunc.demo.detect.camera;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.avutil;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacv.FFmpegLogCallback;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.javacv.OpenCVFrameGrabber;

import java.text.SimpleDateFormat;
import java.util.Date;

import static org.bytedeco.javacpp.opencv_imgproc.CV_FONT_VECTOR0;
import static org.bytedeco.javacpp.opencv_imgproc.putText;

/**
 * 参考连接：https://xinchen.blog.csdn.net/article/details/121572093
 * 摄像头应用的基础类，定义拉流和推流的基本流程，子类只需要实现具的业务方法即可.
 */
@Slf4j
public abstract class AbstractCameraApplication {

    private static final int SECOND = 1000;
    private static final long MILLIS = 1000L;
    private static final int X = 15;
    private static final int Y = 35;
    private static final double FONT_SCALE = 0.8;

    // 摄像头序列号，如果只有一个摄像头，就为0
    protected static final int CAMERA_INDEX = 0;

    // 帧抓取器
    protected FrameGrabber grabber;

    // 输出帧率
    @Getter
    private final double frameRate = 30;

    // 摄像头视频的宽度
    @Getter
    private final int cameraImageWidth = 1280;

    // 摄像头视频的高度
    @Getter
    private final int cameraImageHeight = 720;

    // 转换器
    private final OpenCVFrameConverter.ToIplImage cvFrameConverter =
            new OpenCVFrameConverter.ToIplImage();

    /**
     * 实例化，初始化输出操作相关的资源.
     * 帧处理和输出相关的操作，资源初始化等.
     */
    protected abstract void initOutput() throws Exception;

    /**
     * 输出.
     * 帧处理和输出的具体操作，图片展示等.
     */
    protected abstract void output(Frame frame) throws Exception;

    /**
     * 释放输出操作的相关资源.
     */
    protected abstract void releaseOutputResource() throws Exception;

    /**
     * 两帧之间的间隔时间.
     *
     * @return 间隔时间(秒)
     */
    protected int getInterval() {
        // 假设一秒钟15帧，间隔为（1000/15）
        return (int) (SECOND / frameRate);
    }

    /**
     * 实例化帧抓取器，默认OpenCVFrameGrabber对象.
     */
    protected void instanceGrabber() {
        // 负责从摄像头取数据的是OpenCVFrameGrabber对象，即帧抓取器
        grabber = new OpenCVFrameGrabber(CAMERA_INDEX);
    }

    /**
     * 用帧抓取器抓取一帧，默认调用grab()方法.
     *
     * @return 帧数据
     */
    protected Frame grabFrame() throws Exception {
        return grabber.grab();
    }

    /**
     * 初始化帧抓取器.
     */
    protected void initGrabber() throws Exception {
        // 实例化帧抓取器
        instanceGrabber();

        // 摄像头有可能有多个分辨率，这里指定，也可以调用grabber.getImageWidth获取
        grabber.setImageWidth(cameraImageWidth);
        grabber.setImageHeight(cameraImageHeight);

        // 开启抓取器
        grabber.start();
    }

    /**
     * 不断地从摄像头取帧、处理、输出.
     *
     * @param grabSeconds 抓取时间（秒）
     */
    private void grabAndOutput(int grabSeconds) throws Exception {
        // 添加水印时用到的时间工具
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        long endTime = System.currentTimeMillis() + MILLIS * grabSeconds;

        // 两帧输出之间的时间间隔，默认是1000除以帧率
        int interVal = getInterval();

        // 水印在图片上的位置
        opencv_core.Point point = new opencv_core.Point(X, Y);

        Frame captureFrame;
        opencv_core.Mat mat;

        // 超过指定时间就结束循环
        while (System.currentTimeMillis() < endTime) {
            // 取一帧
            captureFrame = grabFrame();
            if (null == captureFrame) {
                log.error("capture frame is null");
                break;
            }

            // 将帧对象转化为Mat对象
            mat = cvFrameConverter.convertToMat(captureFrame);

            // 在图片上添加水印，水印内容是当前时间，位置为左上角
            putText(mat,
                    simpleDateFormat.format(new Date()),
                    point,
                    CV_FONT_VECTOR0,
                    FONT_SCALE,
                    opencv_core.Scalar.CYAN,
                    1, 0, false);

            // 子类输出
            output(cvFrameConverter.convert(mat));

            // 适当间隔，让肉眼感受不到闪屏即可
            if (interVal > 0) {
                Thread.sleep(interVal);
            }
        }
        log.info("output end");
    }

    /**
     * 释放所有资源.
     */
    private void safeRelease() {
        try {
            // 子类需要释放的资源
            releaseOutputResource();
        } catch (Exception exception) {
            log.error("do releaseOutputResource error", exception);
        } finally {
            if (null != grabber) {
                try {
                    grabber.close();
                } catch (Exception exception) {
                    log.error("close grabber error", exception);
                }
            }
        }
    }

    /**
     * 整合所有初始化操作.
     */
    private void init() throws Exception {
        // 设置ffmpeg日志级别
        avutil.av_log_set_level(avutil.AV_LOG_INFO);
        FFmpegLogCallback.set();

        // 实例化、初始化帧抓取器
        initGrabber();

        // 实例化、初始化输出操作相关的资源
        initOutput();

        long startTime = System.currentTimeMillis();
        log.info("初始化完成，耗时[{}]毫秒，帧率[{}]，图像宽度[{}]，图像高度[{}]",
                System.currentTimeMillis() - startTime,
                frameRate,
                cameraImageWidth,
                cameraImageHeight);
    }

    /**
     * 执行抓取和输出的操作.
     *
     * @param grabSeconds 摄像头抓拍持续时间，程序执行时间
     */
    public void action(int grabSeconds) {
        try {
            // 初始化操作
            init();
            // 持续拉去和推送
            grabAndOutput(grabSeconds);
        } catch (Exception exception) {
            log.error("execute action error", exception);
        } finally {
            safeRelease();
        }
    }

}
